/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#ifndef MATERIALMANAGER_HH_
#define MATERIALMANAGER_HH_

#include <map>

#include "carina/common/global.hh"
#include "carina/common/types.hh"
#include "carina/common/exception.hh"
#include "carina/common/containers.hh"
#include "carina/scene.hh"
#include "carina/math.hh"
#include "carina/common/patterns.hh"
#include "carina/renderer-base.hh"
#include "carina/file-system.hh"

namespace Carina
{
class Scene;

template<class TVar>
class UValuePlaceholder
{
public:
    virtual ~UValuePlaceholder() {}
    virtual void setup(TVar&)=0;
    virtual shared_ptr< UValuePlaceholder<TVar> > clone()=0;
};

template<class TVar, class T>
class UValueHolder: public UValuePlaceholder<TVar>
{
    T m_Data;
public:
    UValueHolder(const T& t)
        :   m_Data(t) {}

    virtual ~UValueHolder() {}

    virtual shared_ptr< UValuePlaceholder<TVar> > clone()
    {
        return make_aligned_shared<UValueHolder>(m_Data);
    }

    virtual void setup(TVar& var)
    {
        var.set(m_Data);
    }
};

template<class TVar>
class UniformValue
{
    shared_ptr< UValuePlaceholder<TVar> >   m_Content;
    UniformVarType                          m_Type;
    size_t                                  m_Len;
public:
    UniformValue()
        :   m_Type(CE_UVT_UNKNOWN),
            m_Len(0) {}

    UniformValue(const UniformValue& a)
    {
        *this = a;
    }

    UniformValue& operator=(const UniformValue& a)
    {
        if(a.m_Content)
            m_Content = a.m_Content->clone();
        m_Type = a.m_Type;
        m_Len = a.m_Len;
        return *this;
    }

    ~UniformValue() {}

    void setTypeCheck(UniformVarType t, size_t len=1) { m_Type = t, m_Len = len; }

    template<typename T>
        UniformValue& operator=(const T& t)
    {
        assert(m_Type != CE_UVT_UNKNOWN);
        if(UVTInfo<T>::value() != m_Type && UVTInfo<T>::length() != m_Len)
            THROW_EXCEPTION("type inconsistency between the shader program and the API");
        typedef UValueHolder<TVar, T> UValueHolderType;
        m_Content = make_aligned_shared<UValueHolderType>(t);
        return *this;
    }

    template<typename T>
        UniformValue& operator=(const DynamicArray<T>& t)
    {
        assert(m_Type != CE_UVT_UNKNOWN);
        if(UVTInfo<T>::value() != m_Type)
            THROW_EXCEPTION("type inconsistency between the shader program and the API");
        if(t.size() != m_Len / UVTInfo<T>::length() || m_Len % UVTInfo<T>::length())
            THROW_EXCEPTION("length inconsistency between the shader program and the API");
        typedef UValueHolder<TVar, DynamicArray<T> > UValueHolderArray;
        m_Content = make_aligned_shared<UValueHolderArray>(t);
        return *this;
    }

    void setup(TVar& var)
    {
        assert(m_Content);
        m_Content->setup(var);
    }
};

typedef std::unordered_map<string, UniformValue<UniformVar> > UniformMap;
typedef std::unordered_map<string, UniformValue<TextureVar> > TextureMap;
template<typename TMap>
    inline void addUniformValue(TMap& map, const string& name, UniformVarType t, size_t len=1)
{
    map[name].setTypeCheck(t, len);
}

struct MaterialProfile
{
    UniformMap      variables;
    TextureMap      textures;
};

class Material
{
    typedef std::unordered_map<string, MaterialProfile>   ProfileMap;
    typedef ProfileMap::value_type                        ProfileInfo;
    ProfileMap          m_ProfileMap;
    ProfileInfo*        m_UVal;
    ShaderProgramPtr    m_Shader;
public:
    Material();
    Material(const ShaderProgramPtr& ptr);
    Material(const Material& mat);

    Material& operator=(const Material& mat);

    ShaderProgramPtr getShaderProgram();

    void addVariables(const MaterialProfile& map);

    void addProfile(const string& profile, const MaterialProfile& map);
    void useProfile(const string& profile);

    template<typename T>
        void set(const string& name, const T& t)
    {
        if(!m_UVal)
            THROW_EXCEPTION("No profile has been specified");
        UniformMap::iterator i = m_UVal->second.variables.find(name);
        if(i == m_UVal->second.variables.end())
            THROW_EXCEPTION("unknown uniform variable: " + name);
        i->second = t;
    }

    void set(const string& name, const TexturePtr& t);

    void setup(const Scene& scene, const Matrix4& model_mat);
};

typedef shared_ptr<Material> MaterialPtr;

class ResourceManager;

class IncludesManager;
typedef shared_ptr<IncludesManager> IncludesManagerPtr; 
class ShaderProgramSet;

class MaterialManager
{
    typedef unique_ptr<ShaderProgramSet, Deleter<ShaderProgramSet>> ShaderProgramSetPtr;
    typedef std::unordered_map<string, ShaderProgramSetPtr> ShaderProgramSetCache;

    RendererPtr           m_Renderer;
    ResourceManager&      m_ResMgr;

    ShaderProgramSetCache m_ShaderProgramSetCache;

    IncludesManagerPtr    m_IncludesManager;
public:
    MaterialManager(ResourceManager& res_mgr);
     ~MaterialManager();

    void getMaterial(Material& mat, const string& name);
    ShaderProgramPtr getShaderProgram(const string& name);
private:
    ShaderProgramSetPtr& getShaderProgramSet(const string& name);
};

struct FileContents
{
    DynamicArray<char> m_Contents;
    size_t             m_References;

    FileContents(size_t size);
};

typedef unique_ptr<FileContents, Deleter<FileContents>> FileContentsPtr;

class IncludesManager: public IncludeLoader
{
    ResourceManager&                            m_ResMgr;

    std::unordered_map<string, FileContentsPtr> m_IncludeCache;
public:
    IncludesManager(ResourceManager& res_mgr);
    virtual ~IncludesManager();
    
    virtual bool loadInclude(const string& filename, const char** contents, size_t& contents_size);
    virtual void freeInclude(const char* contents);
};
}

#endif /* MATERIALMANAGER_HH_ */
